import com.sun.net.httpserver.*;
import java.io.*;
import java.net.InetSocketAddress;
import java.util.*;
import java.util.concurrent.*;
import java.security.SecureRandom;

/**
 * HTTP 投注服务器
 *  1. GET  /<customer>/session  → sessionkey (10min)
 *  2. POST /<offer>/stake?sessionkey=KEY  body=int  → 200
 *  3. GET  /<offer>/highstakes → CSV top-20 per customer
 */
public class BettingHttpServer {
    private static final int PORT = 8001;
    private static final int SESSION_TTL_MS = 10 * 60 * 1000;
    private static final int PER_CUSTOMER_MAX = 20;
    private static final int GLOBAL_MAX = 10_000;

    /* ---------- 入口 ---------- */
    public static void main(String[] args) throws Exception {
        HttpServer server = HttpServer.create(new InetSocketAddress(PORT), 0);
        server.createContext("/", new Handler());
        server.setExecutor(Executors.newCachedThreadPool());
        server.start();
        System.out.println("Listening on port " + PORT);
    }

    /* ---------- 路由 & 分派 ---------- */
    private static class Handler implements HttpHandler {
        private final SessionTable sessions = new SessionTable();
        private final StakeTable stakes = new StakeTable();

        @Override
        public void handle(HttpExchange ex) throws IOException {
            try {
                String method = ex.getRequestMethod();
                String path = ex.getRequestURI().getPath();
                if ("GET".equals(method) && path.endsWith("/session")) {
                    handleSession(ex, path);
                } else if ("POST".equals(method) && path.contains("/stake")) {
                    handleStake(ex, path);
                } else if ("GET".equals(method) && path.endsWith("/highstakes")) {
                    handleHighStakes(ex, path);
                } else {
                    send(ex, 404, "Not Found");
                }
            } catch (Exception e) {
                send(ex, 500, "Internal Error");
            } finally {
                ex.close();
            }
        }

        /* ---- 1. 获取/创建会话 ---- */
        private void handleSession(HttpExchange ex, String path) throws IOException {
            int cust = parseCustomerId(path);
            String key = sessions.getOrCreate(cust);
            send(ex, 200, key);
        }

        /* ---- 2. 下注 ---- */
        private void handleStake(HttpExchange ex, String path) throws IOException {
            int offer = parseOfferId(path);
            String key = parseSessionKey(ex);
            if (key == null || !sessions.isValid(key)) {
                send(ex, 401, "Invalid session");
                return;
            }
            int cust = sessions.customerOf(key);
            int stake = parseStake(ex);
            stakes.addStake(offer, cust, stake);
            send(ex, 200, "");
        }

        /* ---- 3. 高注列表 ---- */
        private void handleHighStakes(HttpExchange ex, String path) throws IOException {
            int offer = parseOfferId(path);
            String csv = stakes.top20csv(offer);
            send(ex, 200, csv);
        }

        /* ---------- 工具 ---------- */
        private void send(HttpExchange ex, int code, String body) throws IOException {
            byte[] raw = body.getBytes();
            ex.sendResponseHeaders(code, raw.length);
            try (OutputStream os = ex.getResponseBody()) {
                os.write(raw);
            }
        }

        private int parseCustomerId(String path) {
            return Integer.parseInt(path.split("/")[1]);
        }

        private int parseOfferId(String path) {
            return Integer.parseInt(path.split("/")[1]);
        }

        private String parseSessionKey(HttpExchange ex) {
            String q = ex.getRequestURI().getQuery();
            if (q != null && q.startsWith("sessionkey="))
                return q.substring(11);
            return null;
        }

        private int parseStake(HttpExchange ex) throws IOException {
            BufferedReader br = new BufferedReader(new InputStreamReader(ex.getRequestBody()));
            return Integer.parseInt(br.readLine().trim());
        }
    }

    /* ---------- 会话表：线程安全 ---------- */
    private static class SessionTable {
        private final ConcurrentHashMap<Integer, Session> map = new ConcurrentHashMap<>();

        private static class Session {
            final String key;
            final long expire;
            Session(String k, long e) { key = k; expire = e; }
        }

        String getOrCreate(int cust) {
            long now = System.currentTimeMillis();
            return map.compute(cust, (k, v) -> {
                if (v != null && v.expire > now) return v;
                String newKey = genKey();
                return new Session(newKey, now + SESSION_TTL_MS);
            }).key;
        }

        boolean isValid(String key) {
            for (Session s : map.values())
                if (s.key.equals(key) && s.expire > System.currentTimeMillis())
                    return true;
            return false;
        }

        int customerOf(String key) {
            for (Map.Entry<Integer, Session> e : map.entrySet())
                if (e.getValue().key.equals(key))
                    return e.getKey();
            return -1;
        }

        private String genKey() {
            String AB = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
            SecureRandom rnd = new SecureRandom();
            StringBuilder sb = new StringBuilder(8);
            for (int i = 0; i < 8; i++) sb.append(AB.charAt(rnd.nextInt(AB.length())));
            return sb.toString();
        }
    }

    /* ---------- 投注表：cust->stake 只保留最高值 ---------- */
    private static class StakeTable {
        private final ConcurrentHashMap<Integer, ConcurrentHashMap<Integer, Integer>> data =
                new ConcurrentHashMap<>();

        void addStake(int offer, int cust, int stake) {
            data.computeIfAbsent(offer, k -> new ConcurrentHashMap<>())
                .merge(cust, stake, Integer::max);
            // 内存保护：简单上限，不逐出旧数据
            if (data.size() > GLOBAL_MAX) data.clear(); // 粗暴保护
        }

        String top20csv(int offer) {
            ConcurrentHashMap<Integer, Integer> m = data.get(offer);
            if (m == null || m.isEmpty()) return "";
            return m.entrySet().stream()
                    .sorted(Map.Entry.<Integer, Integer>comparingByValue().reversed())
                    .limit(20)
                    .map(e -> e.getKey() + "=" + e.getValue())
                    .reduce((a, b) -> a + "," + b).orElse("");
        }
    }
}